前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# koa中间件开发和使用

30 秒速记

  • Koa 中间件 = 洋葱模型:请求一层层进去,响应一层层出来
  • Koa 2 写法 async (ctx, next) => {},await next() 前是前置逻辑,后面是后置逻辑
  • Koa 1 用 function* (next) + yield next,this 就是上下文;Koa 2 要用 koa-convert 包一下才能跑
  • app.use 的注册顺序就是执行顺序,错误处理、计时这类中间件放最前面
  • 易错:忘了 await / return next(),下游的异步结果来不及写进 ctx.body

Koa 中间件说白了就是一串按注册顺序套起来的函数,每个函数通过 await next() 把控制权交给下一层,等下游跑完再回到自己。 所以一个中间件天然分成前后两半:前半段可以做鉴权、记开始时间,后半段可以算耗时、改响应头。Koa 1 时代没有 async,靠 generator 加 co 模拟同样的效果;Koa 2 换成了原生 async/await,老的 generator 中间件得用 koa-convert 转一下。我写中间件一般都套一层工厂函数,配置在外层闭包里处理好,返回真正的 async (ctx, next)。

理解「koa中间件开发和使用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

Koa 的中间件机制核心是 koa-compose,它把 app.use 注册的数组组合成一个函数:第 i 个中间件拿到的 next 就是 () => dispatch(i + 1),返回的永远是 Promise。下面这段可以直接跑,看清洋葱模型的执行顺序:

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
  console.log(1)
  await next()
  console.log(2)
})
app.use(async (ctx, next) => {
  console.log(3)
  await next()
  console.log(4)
})
app.use(async (ctx) => {
  console.log(5)
  ctx.body = 'ok'
})

app.listen(3000)
// 访问一次后输出:1 3 5 4 2
@前端进阶之旅: 代码已经复制到剪贴板

版本上要分清三档:Koa 1 只认 generator 中间件,上下文是 this;Koa 2 用 async (ctx, next),传入 generator 会打废弃警告并自动转换;Koa 3 已经去掉了对 generator 中间件的支持,老插件必须换掉或者自己改写成 async。

面试官追问

追问 1A、B、C 在 next 前打印 1 2 3,之后打印 4 5 6,最终输出什么?
参考回答

1 2 3 6 5 4:前置部分按注册顺序走,后置部分倒过来。这就是洋葱模型,进去是 A → B → C,出来是 C → B → A。

追问 2全局错误处理中间件为什么一定要第一个 use?
参考回答

它要用 try { await next() } catch (err) {} 包住所有下游,只有排在最外层,后面任何一层抛的错才会冒泡到它这里。放在路由后面的话,路由里抛的错它根本接不到。

追问 3某个中间件里没调用 next(),会发生什么?
参考回答

请求在这一层就短路了,后面的中间件和路由都不会执行,响应就是当前已经写进 ctx 的内容。鉴权失败直接 ctx.status = 401 然后不调 next,就是故意利用这一点。

追问 4await next() 和 return next() 有区别吗?
参考回答

都能让下游的 Promise 串进当前中间件,效果一样。区别是 return next() 之后就没有后置逻辑了;要在下游结束后做事,比如统计耗时,就得用 await next()。

追问 5多个中间件之间想共享数据,放哪里?
参考回答

放 ctx.state,这是 Koa 官方约定的命名空间,模板渲染也会读它。比如鉴权中间件写 ctx.state.user,后面的路由直接取,别往 ctx 上随手挂字段,容易和别的插件撞名。

# koa中间件开发和使用

注:原文地址在我的博客issue里 https://github.com/ChenShenhai/blog/issues/15

  • koa v1和v2中使用到的中间件的开发和使用
  • generator 中间件开发在koa v1和v2中使用
  • async await 中间件开发和只能在koa v2中使用

# generator中间件开发

30 秒速记

  • generator 中间件是 Koa 1 的写法:function* (next) {},靠 co 驱动执行
  • yield next 相当于今天的 await next(),把控制权交给下游
  • 上下文是 this,不是参数 ctx,所以不能写成箭头函数
  • Koa 2 里要用 koa-convert 转换,Koa 3 已经不支持
  • 新项目不要再写,读老代码、升级老项目时才需要懂

generator 中间件是 async/await 普及之前的方案,用 yield 暂停函数、让 co 这个库帮你自动往下执行,效果和现在的 await 几乎一样。 当年 Node 还不支持 async,TJ 就用 generator 加 co 让异步代码写得像同步。所以 yield next 你可以直接翻译成 await next(),this 翻译成 ctx。现在学它主要是为了看懂老项目:比如一个 Koa 1 的服务要升级,最常见的活就是把一堆 function* 改成 async (ctx, next)。

理解「generator中间件开发」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1generator 函数自己并不会自动跑完,Koa 1 是怎么让它执行下去的?
参考回答

靠 co。它拿到 generator 的迭代器后不停调 next(),每遇到 yield 一个 Promise 或另一个 generator,就等它完成再把结果塞回去继续执行。async/await 本质上就是语言层面内置了这套自动执行器。

追问 2能不能把 generator 中间件写成箭头函数?
参考回答

不能,箭头函数没有 function* 形式,也没有自己的 this。Koa 1 的上下文就是通过 this 传进来的,写成普通函数也拿不到 ctx。

追问 3接手一个 Koa 1 项目要升 Koa 2,中间件怎么迁?
参考回答

先整体包 koa-convert 让服务跑起来,再逐个改写:function* (next) 改 async (ctx, next),this 改 ctx,yield next 改 await next(),yield somePromise 改 await。改完一个删一个 convert,别长期混用。

追问 4yield next 和 yield* next 有什么不一样?
参考回答

yield* next 直接委托给下游 generator,少一层 co 包装,性能略好,Koa 1 源码里自己也这么用。日常写 yield next 就行,co 会帮你处理。

# generator中间件开发

30 秒速记

  • 写成工厂函数:外层 function () {} 接配置,返回 function* (next) {}
  • 在 generator 里用 this 访问上下文:this.method、this.url、this.header
  • yield next 交出控制权,后面的代码在下游执行完之后才跑
  • if (next) 判断是为了中间件被单独调用时不报错,挂在 Koa 1 上 next 一定有值
  • 忘写 yield next = 请求在这层短路

generator 中间件就是一个返回 generator 函数的工厂函数,里面用 this 拿请求信息,用 yield next 把请求交给下一层。 比如日志中间件,yield next 前面打印请求方法和地址,如果想顺便打印耗时,就在 yield next 后面再记一次时间。外面包一层工厂函数是社区习惯,方便传配置,比如 logger({ level: 'info' }),配置只在启动时处理一次,不用每个请求都算。

generator中间件返回的应该是function * () 函数

/* ./middleware/logger-generator.js */
function log( ctx ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {
    return function * ( next ) {

        
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

koa中间件开发和使用|Nodejs专题系列

# koa中间件开发和使用

30 秒速记

  • Koa 中间件 = 洋葱模型:请求一层层进去,响应一层层出来
  • Koa 2 写法 async (ctx, next) => {},await next() 前是前置逻辑,后面是后置逻辑
  • Koa 1 用 function* (next) + yield next,this 就是上下文;Koa 2 要用 koa-convert 包一下才能跑
  • app.use 的注册顺序就是执行顺序,错误处理、计时这类中间件放最前面
  • 易错:忘了 await / return next(),下游的异步结果来不及写进 ctx.body

Koa 中间件说白了就是一串按注册顺序套起来的函数,每个函数通过 await next() 把控制权交给下一层,等下游跑完再回到自己。 所以一个中间件天然分成前后两半:前半段可以做鉴权、记开始时间,后半段可以算耗时、改响应头。Koa 1 时代没有 async,靠 generator 加 co 模拟同样的效果;Koa 2 换成了原生 async/await,老的 generator 中间件得用 koa-convert 转一下。我写中间件一般都套一层工厂函数,配置在外层闭包里处理好,返回真正的 async (ctx, next)。

理解「koa中间件开发和使用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

Koa 的中间件机制核心是 koa-compose,它把 app.use 注册的数组组合成一个函数:第 i 个中间件拿到的 next 就是 () => dispatch(i + 1),返回的永远是 Promise。下面这段可以直接跑,看清洋葱模型的执行顺序:

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
  console.log(1)
  await next()
  console.log(2)
})
app.use(async (ctx, next) => {
  console.log(3)
  await next()
  console.log(4)
})
app.use(async (ctx) => {
  console.log(5)
  ctx.body = 'ok'
})

app.listen(3000)
// 访问一次后输出:1 3 5 4 2
@前端进阶之旅: 代码已经复制到剪贴板

版本上要分清三档:Koa 1 只认 generator 中间件,上下文是 this;Koa 2 用 async (ctx, next),传入 generator 会打废弃警告并自动转换;Koa 3 已经去掉了对 generator 中间件的支持,老插件必须换掉或者自己改写成 async。

面试官追问

追问 1A、B、C 在 next 前打印 1 2 3,之后打印 4 5 6,最终输出什么?
参考回答

1 2 3 6 5 4:前置部分按注册顺序走,后置部分倒过来。这就是洋葱模型,进去是 A → B → C,出来是 C → B → A。

追问 2全局错误处理中间件为什么一定要第一个 use?
参考回答

它要用 try { await next() } catch (err) {} 包住所有下游,只有排在最外层,后面任何一层抛的错才会冒泡到它这里。放在路由后面的话,路由里抛的错它根本接不到。

追问 3某个中间件里没调用 next(),会发生什么?
参考回答

请求在这一层就短路了,后面的中间件和路由都不会执行,响应就是当前已经写进 ctx 的内容。鉴权失败直接 ctx.status = 401 然后不调 next,就是故意利用这一点。

追问 4await next() 和 return next() 有区别吗?
参考回答

都能让下游的 Promise 串进当前中间件,效果一样。区别是 return next() 之后就没有后置逻辑了;要在下游结束后做事,比如统计耗时,就得用 await next()。

追问 5多个中间件之间想共享数据,放哪里?
参考回答

放 ctx.state,这是 Koa 官方约定的命名空间,模板渲染也会读它。比如鉴权中间件写 ctx.state.user,后面的路由直接取,别往 ctx 上随手挂字段,容易和别的插件撞名。

# koa中间件开发和使用

注:原文地址在我的博客issue里 https://github.com/ChenShenhai/blog/issues/15

  • koa v1和v2中使用到的中间件的开发和使用
  • generator 中间件开发在koa v1和v2中使用
  • async await 中间件开发和只能在koa v2中使用

# generator中间件开发

30 秒速记

  • generator 中间件是 Koa 1 的写法:function* (next) {},靠 co 驱动执行
  • yield next 相当于今天的 await next(),把控制权交给下游
  • 上下文是 this,不是参数 ctx,所以不能写成箭头函数
  • Koa 2 里要用 koa-convert 转换,Koa 3 已经不支持
  • 新项目不要再写,读老代码、升级老项目时才需要懂

generator 中间件是 async/await 普及之前的方案,用 yield 暂停函数、让 co 这个库帮你自动往下执行,效果和现在的 await 几乎一样。 当年 Node 还不支持 async,TJ 就用 generator 加 co 让异步代码写得像同步。所以 yield next 你可以直接翻译成 await next(),this 翻译成 ctx。现在学它主要是为了看懂老项目:比如一个 Koa 1 的服务要升级,最常见的活就是把一堆 function* 改成 async (ctx, next)。

理解「generator中间件开发」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1generator 函数自己并不会自动跑完,Koa 1 是怎么让它执行下去的?
参考回答

靠 co。它拿到 generator 的迭代器后不停调 next(),每遇到 yield 一个 Promise 或另一个 generator,就等它完成再把结果塞回去继续执行。async/await 本质上就是语言层面内置了这套自动执行器。

追问 2能不能把 generator 中间件写成箭头函数?
参考回答

不能,箭头函数没有 function* 形式,也没有自己的 this。Koa 1 的上下文就是通过 this 传进来的,写成普通函数也拿不到 ctx。

追问 3接手一个 Koa 1 项目要升 Koa 2,中间件怎么迁?
参考回答

先整体包 koa-convert 让服务跑起来,再逐个改写:function* (next) 改 async (ctx, next),this 改 ctx,yield next 改 await next(),yield somePromise 改 await。改完一个删一个 convert,别长期混用。

追问 4yield next 和 yield* next 有什么不一样?
参考回答

yield* next 直接委托给下游 generator,少一层 co 包装,性能略好,Koa 1 源码里自己也这么用。日常写 yield next 就行,co 会帮你处理。

# generator中间件开发

30 秒速记

  • 写成工厂函数:外层 function () {} 接配置,返回 function* (next) {}
  • 在 generator 里用 this 访问上下文:this.method、this.url、this.header
  • yield next 交出控制权,后面的代码在下游执行完之后才跑
  • if (next) 判断是为了中间件被单独调用时不报错,挂在 Koa 1 上 next 一定有值
  • 忘写 yield next = 请求在这层短路

generator 中间件就是一个返回 generator 函数的工厂函数,里面用 this 拿请求信息,用 yield next 把请求交给下一层。 比如日志中间件,yield next 前面打印请求方法和地址,如果想顺便打印耗时,就在 yield next 后面再记一次时间。外面包一层工厂函数是社区习惯,方便传配置,比如 logger({ level: 'info' }),配置只在启动时处理一次,不用每个请求都算。

generator中间件返回的应该是function * () 函数

/* ./middleware/logger-generator.js */
function log( ctx ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {
    return function * ( next ) {

        // 执行中间件的操作
        log( this )

        if ( next ) {
            yield next
        }
    }
}
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1想在日志里加上接口耗时,代码怎么改?
参考回答

yield next 前记 const start = Date.now(),后面写 console.log(this.method, this.url, Date.now() - start + 'ms')。因为 yield next 会等下游全部跑完才回来,这时候算出的就是整个下游的耗时。

追问 2为什么要多包一层 module.exports = function () { return function* () {} }?
参考回答

为了能传参数。外层函数接配置、做校验,返回的 generator 通过闭包使用这些配置。直接导出 generator 也能用,只是没法定制。

追问 3中间件里写 this.body = 'xx' 但没 yield next,后面的路由还会执行吗?
参考回答

不会,请求在这里就结束了,响应体就是 xx。拦截类中间件(维护公告、黑名单)就是这么写的,普通日志中间件千万别漏。

追问 4this.header.host + this.url 拿到的地址在反向代理后面准吗?
参考回答

不一定,Nginx 转发时 host 可能被改掉。要拿用户真实访问的域名和 IP,一般开 app.proxy = true,再读 this.host、this.ip,它们会解析 X-Forwarded-* 头。

# generator中间件在koa@1中的使用

← 1.3 koa2简析结构10.1 单元测试 →

fe